/*
 * The MIT License
 *
 * Copyright (c) 2010, Marc Schwerdtfeger (marc@schwerdtfeger.de)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.loosix.core.module
{
import flash.utils.Dictionary;

import org.loosix.core.Loosix;
    import org.loosix.core.lxc_internal;
    import org.loosix.core.module.constant.ModuleState;
import org.loosix.core.module.errors.ModuleManagerError;
import org.loosix.core.module.events.ModuleEvent;
import org.loosix.core.modules.injector.Injector;
import org.loosix.core.utils.ModuleUtil;

use namespace lxc_internal;

    /**
     * @author Marc Schwerdtfeger
     */
    public class ModuleManager
    {
        lxc_internal static const TOPIC:String = "org.loosix.core.module.ModuleManager";
        private static var _instance:ModuleManager;

        private var _moduleMap:Dictionary;

        public function ModuleManager(useLoosix:LoosixEnforcer)
        {
            _moduleMap = new Dictionary();
            registerListeners();


        }  // endConstructor

        /**
         * Registers the module
         *
         * @param module
         */
        public function registerModule(module:Module):void
        {
            module.onPreRegister();

            if (hasModule(module.interfaceName))
            {
                if (module.autoReplace)
                {
                    var oldModule:Module = Loosix.moduleManager.getModule(module.interfaceName);
                    Loosix.loggerService.log("Replacing module '"+oldModule.moduleName+"' with " +
                                              "'"+module.moduleName+"'",this);
                    unregisterModule(module.interfaceName);
                }
                else
                {
                    var msg:String = "ModuleManagerError: interface: '" + module.interfaceName + "' is already registered";
                    Loosix.loggerService.error(msg, this);
                    throw new ModuleManagerError(msg, ModuleManagerError.INTERFACE_IS_ALREADY_REGISTERED);
                }
            } // endIf

            _moduleMap[module.interfaceName] = module;
            module.onRegister();
        } // endIf

        /**
         *  unregisteres the module form the module manager. if other modules are depending on this module
         *  the module is not unregistered, unless force=true.
         *
         * @param interfaceName
         * @param force if force = true the module is stopped and all depending modules are recursivly stopped.
         */
        public function unregisterModule(interfaceName:String, force:Boolean = false):void
        {
            if (hasModule(interfaceName))
            {
                var module:Module = getModule(interfaceName);
                if (dependencyToModule(module))
                {
                    var msg:String = "try to unregister modulemanager (" + module.interfaceName + "). " +
                                     "there are still dependencies to this modulemanager";

                    Loosix.loggerService.warn(msg, this);
                    if (!force)
                    {
                        return;
                    } // endIf

                } // endIf

                module.onUnRegister();
                delete _moduleMap[interfaceName];
              } // endIf
        } // endFunc

        /**
         * Return true if a modulemanager for the given interface is registered.
         * @param interfaceName
         */
        public function hasModule(interfaceName:String):Boolean
        {
            return (_moduleMap[interfaceName] is Module);
        } // endFunc

        /**
         *   Returns the modulemanager by interface name
         * @param interfaceName
         */
        public function getModule(interfaceName:String):Module
        {
           return _moduleMap[interfaceName] as Module;
        } // endFunc

        /**
         *  Returns a list with all registered modules         
         */
        public function getModules():Array
        {
            var moduleList:Array = new Array();

            for each(var module:Module in _moduleMap)
            {
                moduleList.push(module);
            } // endFor

            return moduleList;
        } // endFunc

         /**
         * Return true if the modulemanager for the given interface is started and ready to
         * offer his services.
         * @param interfaceName
         */
        public function hasService(interfaceName:String):Boolean
        {
            if (hasModule(interfaceName))
            {
                var module:Module = getModule(interfaceName);
                return (module.state == ModuleState.STARTED)
            }
            else
            {
                return false;
            } // endIf

        } // endFunc

        /**
         * If the module is started, this method return the service
         * of the module
         * @param interfaceName
         * @return interface of the module service or null
         */
        public function getService(interfaceName:String):*
        {
            var module:Module = getModule(interfaceName);
            if(module && module.state == ModuleState.STARTED)
            {
                return module;
            } // endIf
            return null;
        } // endFunc

        /**
         * the method checks if the modules are resolved after a registration or unregistration
         * of another module.
         *
         * the parameter 'lastAction' tries to save recursive checks.
         * if the parameter is lastAction = 'module_started', a new module is started so we check
         * only the registered modules if the are resolved now
         *
         * if the parameter is lastAction = 'module_stopping', a module is stopping so we check
         * only the started modules if the are still resolved
         *
         * lastAction = null checks both
         *
         * @param lastAction  ModuleEvent.MODULE_STARTED || ModuleEvent.MODULE_REGISTERED || ModuleEvent.MODULE_STOPPING || null
         */
        private function resolveModules(lastAction:String = null):void
        {
            for each (var module:Module in _moduleMap)
            {
                switch (module.state)
                {
                    case ModuleState.REGISTERED:
                        if(lastAction == null ||
                           lastAction == ModuleEvent.MODULE_STARTED ||
                           lastAction == ModuleEvent.MODULE_REGISTERED )
                        {
                            if (isModuleResolved(module))
                                module.onResolved();
                        } // endIf
                    break;
                    case ModuleState.STARTING:
                    case ModuleState.STARTED:
                    case ModuleState.RESOLVED:
                        if(lastAction == null ||
                           lastAction == ModuleEvent.MODULE_STOPPING)
                        {
                            if (!isModuleResolved(module))
                                module.onUnresolved();
                        } // endIf
                    break;
                } // endSwitch
            } // endFor
        } // endFunc

        /**
         * This method returns true if any other module depends on the checked module.
         *
         * @param module
         * @return true if any ohter module depends on this module, else false
         */
        public function dependencyToModule(module:Module):Boolean
        {
            if(module ==  null)
            {
                throw new Error("ModuleManager dependencyToModule: modulemanager is null!");
            } // endIf

            var interfaceName:String = module.interfaceName;
            var dependencies:Array;

            for each(var mod:Module in _moduleMap)
            {
                dependencies = mod.dependencies;
                if (dependencies && dependencies.indexOf(interfaceName) >= 0) return true;
            } // endFor

            return false;
        } // endFunc

        /**
         * Checks if all dependencies for this module are resolved.
         * @param module
         * @return
         */
        public function isModuleResolved(module:Module):Boolean
        {
            for each(var interfaceName:String in module.dependencies)
            {
                if (!hasService(interfaceName)) return false;
            } // endFor

            return true;
        } // endFunc

        lxc_internal static function getInstance():ModuleManager
        {
            if (_instance == null) _instance = new ModuleManager(new LoosixEnforcer());
            return _instance;
        } // endFunc

        /* Eventlisteners */

        private function registerListeners():void
        {
            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_REGISTERED,moduleRegisteredHandler);
            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_RESOLVED,moduleResolvedHandler);

            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_STARTING,moduleStartingHandler);
            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_STARTED,moduleStartedHandler);

            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_STOPPING,moduleStoppingHandler);
            Loosix.topicDispatcher.addEventListener(TOPIC,ModuleEvent.MODULE_UNREGISTERED,moduleUnregisteredHandler);
        } // endFunc

        private function removeListeners():void
        {
            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_REGISTERED,moduleRegisteredHandler);
            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_RESOLVED,moduleResolvedHandler);

            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_STARTING,moduleStartingHandler);
            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_STARTED,moduleStartedHandler);

            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_STOPPING,moduleStoppingHandler);
            Loosix.topicDispatcher.removeEventListener(TOPIC,ModuleEvent.MODULE_UNREGISTERED,moduleUnregisteredHandler);
        } // endFunc

        private function moduleRegisteredHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' registered! " +
                                     "interface: '"+e.module.interfaceName+"'", this);

            broadcastModuleEvent(e);

            if(isModuleResolved(e.module))
            {
                e.module.onResolved();
            } // endIf
        } // endFunc

        private function moduleResolvedHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' resolved!", this);
            broadcastModuleEvent(e);

            // when a implementation of the ModuleInjector is available, we try to inject the requested variables
            var injector:Injector = getService(ModuleUtil.getInterfaceName(Injector));

            // inject into the resolved module
            if (injector) injector.injectInto(e.module);

            // autostart the module if autostart is true             
            if (e.module.autoStart)
            {
                /*
                 we set autostart to false, because when the module is
                 stopped 'stop()' an the internal state is reset to RESOLVED
                 we don�t want the module to restart automatically
                 */
                e.module.autoStart = false;
                e.module.start();
            } // endIf
        } // endFunc

        private function moduleStartedHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' started!", this);
            broadcastModuleEvent(e);
            resolveModules(e.type);
        } // endFunc

        private function moduleStartingHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' starting...", this);
            broadcastModuleEvent(e);
        } // endFunc

        private function moduleStoppingHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' stopping...", this);
            broadcastModuleEvent(e);
            resolveModules(e.type);
        } // endFunc

        private function moduleUnregisteredHandler(e:ModuleEvent):void
        {
            Loosix.loggerService.log("modulemanager: '" + e.module.moduleName + "' unregistered! " +
                                     "interface: '" + e.module.interfaceName + "'", this);
            broadcastModuleEvent(e);
        } // endFunc

        private function broadcastModuleEvent(event:ModuleEvent):void
        {
            event.stopImmediatePropagation();
            Loosix.topicDispatcher.dispatchEvent(ModuleEvent.TOPIC, event.clone());
        } // endFunc
                
    }//endClass

} // endPackage
class LoosixEnforcer
{
}// endClass